package com.company.aspect;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.persistence.Id;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.HibernateProxyHelper;
import org.springframework.util.ReflectionUtils;

import com.company.aspect.annotation.LogField;
import com.company.aspect.annotation.LogParam;

public class LoggingHelper {
	public NewValuesInfo extractNewValues(ProceedingJoinPoint pjp) {
		NewValuesInfo newValuesInfo = new NewValuesInfo();
		Map<String, Object> mapNewValues = new HashMap<String, Object>();
		int idxParam = 0;
		
		MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
		Method method = methodSignature.getMethod();
		Class[] paramTypes = method.getParameterTypes();
		
		Object[] objectsNewValues = pjp.getArgs();
		
		Annotation[][] paramAnnotations = method.getParameterAnnotations();
        for(int i=0; i < paramAnnotations.length; i++) {        	        	
			for (Annotation annotation : paramAnnotations[i]) {
				if(annotation instanceof LogParam) {	
					idxParam = i;
					newValuesInfo.setIdxParam(idxParam);
					
					Class paramType = paramTypes[i];		
					Field[] fields = paramType.getDeclaredFields();
					
					for (Field field : fields) {
						if(field.isAnnotationPresent(Id.class)) {														
							ReflectionUtils.makeAccessible(field);
							
							Serializable id = (Serializable) ReflectionUtils.getField(field, objectsNewValues[i]);
							Class fieldClass = field.getType();
							
							newValuesInfo.setModifiedObject(objectsNewValues[i]);
							newValuesInfo.setIdClassType(fieldClass);
							newValuesInfo.setIdFieldValue(id);
							
						} else if(field.isAnnotationPresent(LogField.class)) {																							
							ReflectionUtils.makeAccessible(field);
							
							String fieldName = field.getName();
							Object fieldValue = ReflectionUtils.getField(field, objectsNewValues[i]);
							
							mapNewValues.put(fieldName, fieldValue);
						} 
					}
					
					newValuesInfo.setMapNewValues(mapNewValues);
				} 
			}
		}
		
		return newValuesInfo;
	}
	
	public OldValuesInfo extractOldValues(ProceedingJoinPoint pjp, NewValuesInfo newValuesInfo) {
		OldValuesInfo oldValuesInfo = new OldValuesInfo();
		Map<String, Object> mapOldValues = new HashMap<String, Object>();
		
		Class dao = pjp.getSignature().getDeclaringType();
		oldValuesInfo.setDaoClass(dao);
		
		if(newValuesInfo.getIdClassType() != null) {			
			
			Method method = ReflectionUtils.findMethod(dao, "findById", newValuesInfo.getIdClassType());
	        
			if(method != null && newValuesInfo.getIdFieldValue() != null) {
				try {					
					Object returnObject = ReflectionUtils.invokeMethod(method, pjp.getTarget(), newValuesInfo.getIdFieldValue());									
					
					if(returnObject != null) {		
						Class oldValueClass = HibernateProxyHelper.getClassWithoutInitializingProxy(returnObject);	
						Object nonProxy = null;
						
						Hibernate.initialize(returnObject); 	
						if(returnObject instanceof HibernateProxy) {
							nonProxy = ((HibernateProxy) returnObject).getHibernateLazyInitializer().getImplementation();														
						}
						
						Field[] fields = oldValueClass.getDeclaredFields();														
						
						for (Field field : fields) {
							if(field.isAnnotationPresent(LogField.class)) {																							
								ReflectionUtils.makeAccessible(field);
								
								String fieldName = field.getName();
								Object fieldValue = ReflectionUtils.getField(field, nonProxy);																
								
								mapOldValues.put(fieldName, fieldValue);
							} 
						}
						
						oldValuesInfo.setReturnObject(nonProxy);
						oldValuesInfo.setMapOldValues(mapOldValues);
					}					
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} 
			}
		}
		
		return oldValuesInfo;
	}
	
	public Map<String, String> compareValues(OldValuesInfo oldValuesInfo, NewValuesInfo newValuesInfo)  { 
		Map<String, String> mapDiffValues = new HashMap<String, String>();
		
		Map<String, Object> oldValues = oldValuesInfo.getMapOldValues();
		Map<String, Object> newValues = newValuesInfo.getMapNewValues();
		
		if(oldValues != null && !oldValues.isEmpty()
				&& newValues != null && !newValues.isEmpty()) {					
			
			Set<String> setFieldsName = newValues.keySet();
			for (String fieldName : setFieldsName) {
				Object oldValue = oldValues.get(fieldName);
				Object newValue = newValues.get(fieldName);
				
				if(oldValue != null && newValue != null && !oldValue.equals(newValue)) { 
					String diffInfo = oldValue + " -> " + newValue;
					
					mapDiffValues.put(fieldName, diffInfo);
				}
			}
		}
		
		return mapDiffValues;
	}
	
	public Object[] setNewValues(Object[] originalObjects, OldValuesInfo oldValuesInfo, NewValuesInfo newValuesInfo) {
		if(originalObjects != null && originalObjects.length > 0) {
			for(int i=0; i < originalObjects.length; i++) {
				if(i == newValuesInfo.getIdxParam()) {					
					Object oldValue = oldValuesInfo.getReturnObject();
					Object newValue = newValuesInfo.getModifiedObject();
					
					Field[] fields = oldValue.getClass().getDeclaredFields();														
					
					for (Field field : fields) {										
						ReflectionUtils.makeAccessible(field);
						
						if(!Modifier.isStatic(field.getModifiers())) {
							Object newFieldValue = ReflectionUtils.getField(field, newValue);
							
							ReflectionUtils.setField(field, oldValue, newFieldValue);
						}												
					}
					
					originalObjects[i] = oldValue;
				}
			}
		}
		
		return originalObjects;
	}
}
